package androidawn.framework.control;

import java.lang.reflect.Method;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import androidawn.framework.constant.LogConst;

public class ThreadDispatcher implements Runnable, Handler.Callback {
	
	private Handler handler;
	
	private Thread thread;
	
	private Queue<MethodClosure> closures = new ConcurrentLinkedQueue<MethodClosure>();
	
	public ThreadDispatcher()
	{	
		thread = new Thread(this);
		thread.start();
	}

	/**
	 * Invoke the method in another thread
	 * @param method
	 * @param args
	 */
	public void asynInvoke(Object target, String method, Class<?>[] parameterTypes,  Object ...parameters)
	{	
		closures.add(new MethodClosure(target, method, parameterTypes, parameters));
		if(handler == null)
		{
			return;
		}
		handler.sendEmptyMessage(1);
	}
	
	@Override
	public void run() {
		Looper.prepare();
		handler = new Handler(this);
		Message msg = new Message();
		msg.what  = 1;
		while(closures.size() > 0)
		{
			handleMessage(msg);
		}
		Looper.loop();
	}

	@Override
	public boolean handleMessage(Message msg) {
		if(msg.what != 1)
		{
			return false;
		}
		MethodClosure mc = closures.poll();
		if(mc == null)
		{
			return  false;
		}
		try 
		{
			Method method = getDeclaredMethod(mc.target.getClass(), mc.method, mc.parameterTypes);
			if(method == null)
			{
				return false;
			}
			boolean oldFlag = method.isAccessible();
			method.setAccessible(true);
			method.invoke(mc.target, mc.parameters);
			method.setAccessible(oldFlag);
		} catch (Exception e)
		{
			Log.w(LogConst.WARN, e.getMessage());
		}
		return true;
	}
	
	private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] parameterTypes)
	{
		Method result = null;
		try 
		{
			result = clazz.getDeclaredMethod(name, parameterTypes);
		} catch (Exception e)
		{
			Log.w(LogConst.WARN, e.getMessage());
		}
		if(result == null && !clazz.equals(Object.class))
		{
			result = getDeclaredMethod(clazz.getSuperclass(), name,  parameterTypes);
		}
		return result;
	}
}

class MethodClosure
{
	public Object target;
	public String method;
	public Class<?>[] parameterTypes;
	public Object[] parameters;
	public MethodClosure(Object target, String method, Class<?>[] parameterTypes,  Object ...parameters)
	{
		this.target = target;
		this.method = method;
		this.parameterTypes = parameterTypes;
		this.parameters = parameters;
	}
}
